#include "batchgetcommand.h"

#include "filemanager/filemanager.h"

#include <iostream>
#include <sstream>
using namespace std;

#include <Poco/DOM/Document.h>
#include <Poco/DOM/DOMWriter.h>
#include <Poco/DOM/DOMParser.h>
#include <Poco/DOM/Element.h>
#include <Poco/DOM/NodeList.h>
using Poco::XML::Document;
using Poco::XML::DOMWriter;
using Poco::XML::DOMParser;
using Poco::XML::Element;
using Poco::XML::NodeList;

#include <Poco/Exception.h>
using Poco::Exception;

namespace Commands {

	string const BatchGetCommand::NAME = "BatchGet";

	BatchGetCommand::BatchGetCommand(Files const& files, set<id_type> visited, NodeData* data)
		: NodeCommand(data, true)
		  , _files(files)
		  , _visited(visited)
	{
	}

	string BatchGetCommand::repr()
	{
		ostringstream out;
		DOMWriter w;
		w.writeNode(out, toXML());
		return out.str();
	}

	string BatchGetCommand::name()
	{
		return string(BatchGetCommand::NAME);
	}

	CommandCreator BatchGetCommand::creator()
	{
		return &BatchGetCommand::makeCommand;
	}

	Document* BatchGetCommand::toXML(Document* )
	{
		Document* doc = new Document();

		for (Files::iterator it = _files.begin(); it != _files.end(); ++it)
		{
			Element* file = doc->createElement("file");
			file->setAttribute("name", it->first);
			doc->appendChild(file);
		}

		Element* visited = doc->createElement("visited");
		for (set<id_type>::iterator it = _visited.begin(); it != _visited.end(); ++it)
		{
			Element* node = doc->createElement("node");
			ostringstream ss; ss << *it;
			node->setAttribute("id", ss.str());
			visited->appendChild(node);
		}
		doc->appendChild(visited);

		return NodeCommand::toXML(doc);
	}

	Command* BatchGetCommand::makeCommand(string r, void* d)
	{
		NodeData* data = (NodeData*) d;
		Files files;
		set<id_type> visited;
		DOMParser p;
		Document* doc = p.parseString(r);

		FileManager fmgr;
		
		cout << "BatchGetCommand :: makeCommand :: We have a doc? " << (doc ? "yes" : "no") << endl;
		NodeList* ff = doc->getElementsByTagName("file");
		cout << "BatchGetCommand :: makeCommand :: We have a nodelist? " << (ff ? "yes" : "no") << endl;
		for(unsigned long i = 0; i < ff->length(); ++i)
		{
			Element* e = (Element*) (ff->item(i));
			cout << "BatchGetCommand :: makeCommand :: We have a element? " << (e ? "yes" : "no") << endl;
			string filename = e->getAttribute("name");
			cout << "BatchGetCommand :: makeCommand :: filename :: " << filename << endl;
			File ffff(filename);
			cout << "BatchGetCommand :: makeCommand :: We have a file? " << (ffff.exists() ? "yes" : "no") << endl;
			files[filename] = ffff;
		}

		NodeList* ids = doc->getElementsByTagName("node");
		for (unsigned long i = 0; i < ids->length(); ++i)
		{
			Element* node = (Element*)(ids->item(i));
			id_type id;
			istringstream(node->getAttribute("id")) >> id;
			visited.insert(id);
		}

		return new BatchGetCommand(files, visited, data);
	}

	void BatchGetCommand::execute()
	{
		cout << "BatchGetCommand :: going to find files that don't need forwarding" << endl;

		// See if insertion resulted in a false value in the pair<K,bool> returned by set<K>::insert(K)
		if (!(_visited.insert(nodeId()).second))
		{
			setResult("OK");
			return;
		}

		Files failers;
		Files winners;

		for (Files::iterator it = _files.begin(); it != _files.end(); ++it)
		{
			UInt32 h = FileManager::hash(it->first);
			UInt32 n = nodeId();
			UInt32 i = prevId();
			cout << "BatchGetCommand :: Node | hash | Next :: " << i << " | " << h << " | " << n << endl;
			if (i < h && h <= n)
			{
				cout << "BatchGetCommand :: Found file #1 :: (" << i << ", " << h << ", " << n << ")" << endl;
				winners[it->first] = it->second;
			} else if (i > n && ((h < i && h <= n) || (h > i && h > n)))
			{
				/*
				 * Some info on this mayor comparison:
				 *
				 * when at the ID wrap-around, previous
				 * comparison does not hold. If the file
				 * belongs in this interval, it is either
				 * larger than the node ID and the prev ID or
				 * it is smaller than both of them.
				 */
				cout << "BatchGetCommand :: Found file #2 :: (" << i << ", " << h << ", " << n << ")" << endl;
				winners[it->first] = it->second;
			} else 
			{
				cout << "BatchGetCommand :: Searching file :: (" << i << ", " << h << ", " << n << ")" << endl;
				failers[it->first] = it->second;
			}
		}

		cout << "BatchGetCommand :: " << failers.size() << " files left to get" << endl;

		if (failers.size() > 0)
		{
			_files = failers;

			cout << "BatchGetCommand :: Continuing BatchGet" << endl;
			BatchGetCommand batch(failers, _visited);
			master()->executeCommand(&batch);

			batch.result();
			
			for (Files::iterator it = _files.begin(); it != _files.end(); ++it)
			{
				cout << "BatchGetCommand :: Getting '" << it->first << "'" << endl;
				node()->get(it->first, it->second);
				cout << "BatchGetCommand :: Getted '" << it->first << "'" << endl;
			}

		}
		setResult("OK");

		cout << "BatchGetCommand :: Done!" << endl;
	}

}
